package com.loanafter.fox.monitor.logback;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.AppenderBase;
import ch.qos.logback.core.filter.Filter;
import com.loanafter.fox.monitor.logback.connection.Connection;
import com.loanafter.fox.monitor.logback.connection.HttpConnection;
import com.loanafter.fox.monitor.logback.event.Event;
import com.loanafter.fox.monitor.logback.event.EventBuilder;
import com.loanafter.fox.monitor.logback.filter.MessagePrefixFilter;
import com.loanafter.fox.monitor.logback.util.Util;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author jasonli@kuainiugroup.com
 * @date 2020/7/30 下午4:27
 */
public class FoxMonitorAppend extends AppenderBase<ILoggingEvent> {

    protected String url;
    protected Map<String,String> tags = Collections.emptyMap();
    protected volatile Connection connection;

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }


    public Map<String, String> getTags() {
        return tags;
    }

    public void setTags(String tags) {
        this.tags = Util.parseTags(tags);
    }



    /**
     * Used for lazy initialization of appender state, see {@link #lazyInit()}.
     */
    private volatile boolean initialized = false;

    @Override
    protected void append(ILoggingEvent iLoggingEvent) {
        if(!isStarted()){
            return;
        }
        try {
            lazyInit();
            Event event = buildEvent(iLoggingEvent);
            connection.send(event);
        } catch (Exception e) {
            addError("An exception occurred while creating a new event in Fox monitor",e);
        }
    }

    public synchronized void stop() {
        // The synchronized modifier avoids concurrent append and close operations
        if (!this.started) {
            return;
        }

        this.started = false;
    }

    private void lazyInit() {
        if (!initialized) {
            synchronized (this) {
                if (!initialized) {
                    try {
                        initConnection();
                    } finally {
                        initialized = true;
                    }
                }
            }
        }
    }

    public void initConnection() {

        this.connection = new HttpConnection(url);

    }

    /**
     * Builds an Event based on the logging event.
     *
     * @param iLoggingEvent Log generated.
     * @return Event containing details provided by the logging system.
     */
    protected Event buildEvent(ILoggingEvent iLoggingEvent) {
        EventBuilder eventBuilder = new EventBuilder()
                .withTimestamp(new Date(iLoggingEvent.getTimeStamp()))
                .withMessage(iLoggingEvent.getFormattedMessage())
                .withLogger(iLoggingEvent.getLoggerName())
                .withLevel(formatLevel(iLoggingEvent.getLevel()));

        List<MessagePrefixFilter> filterList = Optional.ofNullable(getCopyOfAttachedFiltersList())
                .orElse(new ArrayList<>())
                .stream()
                .filter(f -> f instanceof MessagePrefixFilter)
                .map(f -> (MessagePrefixFilter) f)
                .collect(Collectors.toList());

        eventBuilder.withMessagePrefix(filterList);


        for (Map.Entry<String, String> tagEntry : tags.entrySet()) {
            eventBuilder.withTag(tagEntry.getKey(), tagEntry.getValue());
        }

        return eventBuilder.build();
    }

    /**
     * Transforms a {@link Level} into an {@link Event.Level}.
     *
     * @param level original level as defined in logback.
     * @return log level used within raven.
     */
    protected static Event.Level formatLevel(Level level) {
        if (level.isGreaterOrEqual(Level.ERROR)) {
            return Event.Level.ERROR;
        } else if (level.isGreaterOrEqual(Level.WARN)) {
            return Event.Level.WARNING;
        } else if (level.isGreaterOrEqual(Level.INFO)) {
            return Event.Level.INFO;
        } else if (level.isGreaterOrEqual(Level.ALL)) {
            return Event.Level.DEBUG;
        } else {
            return null;
        }
    }
}
